package com.powerwyx.shortlink.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.powerwyx.shortlink.admin.common.biz.user.UserContext;
import com.powerwyx.shortlink.admin.common.constant.RedisConstant;
import com.powerwyx.shortlink.admin.common.convention.errorcode.BaseErrorCode;
import com.powerwyx.shortlink.admin.common.convention.exception.ClientException;
import com.powerwyx.shortlink.admin.common.enums.UserErrorCodeEnum;
import com.powerwyx.shortlink.admin.mapper.UserMapper;
import com.powerwyx.shortlink.admin.pojo.dto.UserLoginDTO;
import com.powerwyx.shortlink.admin.pojo.dto.UserRegisterDTO;
import com.powerwyx.shortlink.admin.pojo.dto.UserUpdateDTO;
import com.powerwyx.shortlink.admin.pojo.entity.User;
import com.powerwyx.shortlink.admin.pojo.vo.UserLoginVO;
import com.powerwyx.shortlink.admin.pojo.vo.UserVO;
import com.powerwyx.shortlink.admin.service.GroupService;
import com.powerwyx.shortlink.admin.service.UserService;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.powerwyx.shortlink.admin.common.constant.RedisConstant.LOCK_USER_REGISTER_KEY;

/**
 * @author 17585
 * @description 针对表【t_user】的数据库操作Service实现
 * @createDate 2024-06-28 17:15:16
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RBloomFilter<String> userRegisterCachePenetrationBloomFilter;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private GroupService groupService;

    /**
     * 根据用户名查询用户信息
     */
    @Override
    public UserVO getUserByUserName(String username) {
        if (username == null) {
            throw new ClientException(UserErrorCodeEnum.USER_NOT_EXIST);
        }
        User user = getOne(new QueryWrapper<User>().eq("username", username));
        if (user == null) {
            throw new ClientException(UserErrorCodeEnum.USER_NOT_EXIST);
        }

        return BeanUtil.copyProperties(user, UserVO.class);
    }


    /**
     * 查询用户名是否存在
     */
    @Override
    public Boolean hasUserName(String username) {
        if (username == null) {
            throw new ClientException(BaseErrorCode.USER_NAME_VERIFY_ERROR);
        }
        //使用布隆过滤器判断用户名是否已经存在
        return userRegisterCachePenetrationBloomFilter.contains(username);
    }

    /**
     * 注册用户
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void register(UserRegisterDTO userRegisterDTO) {
        if (userRegisterDTO == null) {
            throw new ClientException(BaseErrorCode.PARAMS_ERROR);
        }
        //查询用户名是否已存在
        if (hasUserName(userRegisterDTO.getUsername())) {
            throw new ClientException(BaseErrorCode.USER_NAME_EXIST_ERROR);
        }
        //分布式锁，防止并发请求
        RLock lock = redissonClient.getLock(LOCK_USER_REGISTER_KEY + userRegisterDTO.getUsername());
        //如果获取锁不成功
        if (!lock.tryLock()) {
            throw new ClientException(BaseErrorCode.USER_NAME_EXIST_ERROR);
        }
        //如果获取成功
        try {
            User user = BeanUtil.copyProperties(userRegisterDTO, User.class);
            if (!save(user)) {
                throw new ClientException(BaseErrorCode.USER_REGISTER_ERROR);
            }
            //用户注册完成，添加默认分组
            groupService.saveGroup("默认分组", userRegisterDTO.getUsername());
            //如果注册成功，将用户名添加至布隆过滤器
            userRegisterCachePenetrationBloomFilter.add(userRegisterDTO.getUsername());
        } finally {
            lock.unlock();
        }


    }


    /**
     * 修改用户信息
     */
    @Override
    public void updateUserInfo(UserUpdateDTO userUpdateDTO) {
        if (userUpdateDTO == null) {
            throw new ClientException(BaseErrorCode.PARAMS_ERROR);
        }
        if (!Objects.equals(UserContext.getUsername(), userUpdateDTO.getUsername())) {
            throw new ClientException("用户名信息错误!");
        }
        User user = BeanUtil.copyProperties(userUpdateDTO, User.class);
        boolean isSuccess = update(user, new QueryWrapper<User>().eq("username", user.getUsername()));
        if (!isSuccess) {
            throw new ClientException(BaseErrorCode.OPERATION_ERROR);
        }
    }


    /**
     * 用户登录
     */
    @Override
    public UserLoginVO login(UserLoginDTO userLoginDTO) {
        if (userLoginDTO == null) {
            throw new ClientException(BaseErrorCode.PARAMS_ERROR);
        }
        User user = getOne(new QueryWrapper<User>()
                .eq("username", userLoginDTO.getUsername())
                .eq("password", userLoginDTO.getPassword()));
        if (user == null) {
            throw new ClientException("用户名或密码错误!");
        }
        //如果用户已登录
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(RedisConstant.LOGIN_KEY + userLoginDTO.getUsername());
        if (CollUtil.isNotEmpty(entries)) {
            String token = entries.keySet().iterator().next().toString();
            stringRedisTemplate.expire(RedisConstant.LOGIN_KEY + userLoginDTO.getUsername(), 30L, TimeUnit.MINUTES);
            return new UserLoginVO(token);
        }
        String token = UUID.randomUUID().toString(true);
        //将token信息存入redis，设置有效时间为30分钟
        stringRedisTemplate.opsForHash().put(RedisConstant.LOGIN_KEY + userLoginDTO.getUsername(), token, JSONUtil.toJsonStr(user));
        stringRedisTemplate.expire(RedisConstant.LOGIN_KEY + userLoginDTO.getUsername(), 30L, TimeUnit.MINUTES);
        return new UserLoginVO(token);
    }


    /**
     * 检查用户是否登录
     */
    @Override
    public Boolean checkUserLogin(String username, String token) {
        return stringRedisTemplate.opsForHash().get(RedisConstant.LOGIN_KEY + username, token) != null;
    }


    /**
     * 用户退出
     */
    @Override
    public void logout(String username, String token) {
        if (username == null || token == null) {
            throw new ClientException(BaseErrorCode.PARAMS_ERROR);
        }
        if (checkUserLogin(username, token)) {
            stringRedisTemplate.delete(RedisConstant.LOGIN_KEY + username);
            return;
        }
        throw new ClientException("用户Token不存在或用户未登录!");
    }


}




